package com.road.yishi.mina;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.executor.ExecutorFilter;
import org.apache.mina.transport.socket.SocketConnector;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.road.yishi.mina.codec.protobuf.ProtobufCodecFactory;
import com.road.yishi.pb.cross.LoadMsgProto.LoadMsg;
import com.road.yishi.proto.PBMessage;

/**
 * 服务器和网关服务器之间的连接
 */
public final class LinkedClient {

	private static final Logger Log = LoggerFactory.getLogger(LinkedClient.class);

	/* 网关与客户端之间的Session */
	public static final String TEMP_SESSION_USER_ID = "TEMP_SESSION_USER_ID";
	public static final String TEMP_SESSION_KEY = "TEMP_SESSION_KEY";
	public static final String KEY_ID = "KEY_CLIENT_ID";
	/* 网关与各个Server之间的Session */
	public static final String KEY_CLIENT = "KEY_CLIENT";
	/* 网关与跨服之间的Session，断开后是否重连 */
	public static final String KEY_NO_RETRY = "NO_RETRY";

	private int id;
	private int type; // 连接类型
	private String name;
	private String address;
	private int port;
	private int load;// 负载
	private long lastUpdate;
	private boolean isActive;// 是否激活
	private volatile boolean isTry;
	private int connTimes;
	private int index;
	// 连接会话
	private IoSession session = null;
	// 连接
	private SocketConnector connector = null;
	// 消息处理器
	private IoHandlerAdapter handler = null;

	public LinkedClient(int type, int id, String name, String address, int port, IoHandlerAdapter handler, int index) {
		this.type = type;
		this.id = id;
		this.name = name;
		this.address = address;
		this.port = port;
		this.handler = handler;
		this.load = 0;
		this.lastUpdate = 0;
		this.isTry = true;
		this.connTimes = 0;
		this.index = index;
	}

	public LinkedClient(int type, int id, String name, String address, int port, boolean isActive) {
		this.type = type;
		this.id = id;
		this.name = name;
		this.address = address;
		this.port = port;
		this.isActive = isActive;
		this.load = 0;
		this.lastUpdate = 0;
		this.isTry = true;
		this.connTimes = 0;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getAddress() {
		return address;
	}

	public int getPort() {
		return port;
	}

	public void setSession(IoSession session) {
		this.session = session;
	}

	public IoSession getSession() {
		return session;
	}

	public SocketConnector getConnector() {
		return connector;
	}

	public int getType() {
		return type;
	}

	public String getName() {
		return name;
	}

	public int getLoad() {
		return load;
	}

	public void setLoad(int load) {
		this.load = load;
		lastUpdate = System.currentTimeMillis();
	}

	public long getLastUpdate() {
		return lastUpdate;
	}

	public void setActive(boolean isActive) {
		this.isActive = isActive;
	}

	public boolean isActive() {
		return isActive;
	}

	public boolean isTry() {
		return isTry;
	}

	public void setTry(boolean isTry) {
		this.isTry = isTry;
	}

	public int getConnTimes() {
		return connTimes;
	}

	public void setConnTimes(int connTimes) {
		this.connTimes = connTimes;
	}

	public void resetConnTimes() {
		connTimes = 0;
	}

	public int getIndex() {
		return index;
	}

	public boolean isLinkedClient(int serverId, String name, String address, int port) {
		return this.id == serverId && this.name.equals(name) && this.address.equals(address) && this.port == port;
	}

	// public LoadMsg.Builder writeProto() {
	// LoadMsg.Builder msg = LoadMsg.newBuilder();
	// msg.setServerId(id);
	// msg.setServerName(name == null ? "" : name);
	// msg.setAddress(address == null ? "" : address);
	// msg.setPort(port);
	// msg.setLoad(load);
	// msg.setType(type);
	// msg.setConnTimes(connTimes);
	// msg.setIndex(index);
	// return msg;
	// }

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("id : ").append(id);
		sb.append(", name : ").append(name);
		sb.append(", index : ").append(index);
		sb.append(", address : ").append(address);
		sb.append(", port : ").append(port);
		sb.append(", load : ").append(load);
		sb.append(", isActive : ").append(isActive);
		sb.append(", connTimes : ").append(connTimes);
		if (session != null) {
			sb.append(", : ").append(session.getRemoteAddress().toString());
		}
		return sb.toString();
	}

	/**
	 * 是否连接
	 * 
	 * @return
	 */
	public synchronized boolean isConnected() {
		if (session != null && session.isConnected()) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * 连接
	 */
	public synchronized boolean connect() {
		try {
			// load = 0;

			connector = new NioSocketConnector(Runtime.getRuntime().availableProcessors() + 1);
			connector.setConnectTimeoutMillis(1000 * 8);
			connector.setHandler(handler);
			connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ProtobufCodecFactory()));

			connector.getFilterChain().addLast("executor", new ExecutorFilter(Executors.newSingleThreadExecutor(new ThreadFactory() {

				public Thread newThread(Runnable r) {
					Thread thread = new Thread(r, address + "-" + port + "-thread");
					return thread;
				}
			})));

			ConnectFuture future = connector.connect(new InetSocketAddress(address, port));
			future.awaitUninterruptibly(1000 * 10);
			session = future.getSession();
			session.setAttribute(KEY_CLIENT, this);
			connTimes = 1;
			return true;
		} catch (Exception e) {
			connector.dispose();
			connector = null;
			Log.error("connect to address " + address + ":" + port + " fail.", e);
			connTimes++;
			return false;
		}
	}

	public synchronized void disConnect() {
		if (isConnected()) {
			session.close(true);
			session = null;
		}

		if (connector != null) {
			connector.dispose();
			connector = null;
		}
	}

	/**
	 * 发送数据包
	 * 
	 * @param packet
	 */
	public void send(PBMessage packet) {
		if (session == null || !session.isConnected()) {
			return;
		}

		try {
			session.write(packet);
		} catch (Exception e) {
			Log.warn(" session write error . packet : " + packet.toString(), e);
		}
	}

	public void send(int userId, PBMessage packet) {
		packet.setPlayerId(userId);
		send(packet);
	}

	public LoadMsg.Builder writeProto() {
		LoadMsg.Builder msg = LoadMsg.newBuilder();
		msg.setServerId(id);
		msg.setServerName(name == null ? "" : name);
		msg.setAddress(address == null ? "" : address);
		msg.setPort(port);
		msg.setLoad(load);
		msg.setType(type);
		msg.setConnTimes(connTimes);
		msg.setIndex(index);
		return msg;
	}

}
